import threading
import time
import tkinter as tk
from pathlib import Path
from typing import Optional
import logging

from .asr import record_audio_stoppable, transcribe_with_whisper, preprocess_text
from .config import Config
from .executor import ActionExecutor
from .llm import DoubaoLLM
from .tts import SpeechFeedback
from .logger import log_event
import math


class PetController:
    def __init__(self):
        self.llm = DoubaoLLM()
        self.executor = ActionExecutor(llm=self.llm)
        self.speaker = SpeechFeedback()
        self.interacting = False
        self._thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()
        # 新增：区分取消与完成
        self._cancel_requested = False

    def start_interaction(self, max_duration: Optional[float] = None):
        if self.interacting:
            return
        self._stop_event.clear()
        # 开始前清除取消标记
        self._cancel_requested = False
        self.interacting = True
        max_d = Config.INTERACTION_MAX_DURATION if max_duration is None else max_duration
        log_event("INTERACTION_START", f"max_duration={max_d}")
        self._thread = threading.Thread(target=self._voice_flow_thread, args=(max_d,), daemon=True)
        self._thread.start()

    def cancel_interaction(self):
        if not self.interacting:
            return
        # Request cancellation and stop any ongoing TTS
        log_event("INTERACTION_CANCEL_REQUEST", "stop_event set and TTS stop invoked")
        self._cancel_requested = True
        self._stop_event.set()
        try:
            self.speaker.stop()
        except Exception:
            pass
        # interacting will be reset by the worker thread when it exits

    # 新增：停止录音并继续处理
    def finish_interaction(self):
        if not self.interacting:
            return
        log_event("INTERACTION_STOP_REQUEST", "stop_event set (finish recording)")
        # 不设置取消标记，仅通知录音停止
        self._cancel_requested = False
        self._stop_event.set()

    def _voice_flow_thread(self, max_duration: float):
        try:
            # 1) Record (stoppable)
            audio_path = Config.OUTPUT_DIR / "pet_record.wav"
            record_audio_stoppable(audio_path, stop_event=self._stop_event, max_duration=max_duration)
            # 若用户请求取消，直接退出；若只是停止录音，则继续后续流程
            if self._stop_event.is_set() and self._cancel_requested:
                log_event("INTERACTION_CANCELLED", "recording stopped by user")
                return
            if not audio_path.exists() or audio_path.stat().st_size == 0:
                log_event("INTERACTION_EMPTY", "no audio captured")
                return
            # 2) Transcribe
            text = transcribe_with_whisper(audio_path, language="zh")
            text = preprocess_text(text)
            if self._stop_event.is_set() and self._cancel_requested:
                log_event("INTERACTION_CANCELLED", "after ASR")
                return
            if not text:
                log_event("INTERACTION_EMPTY", "no text recognized")
                return
            # 3) Parse actions
            actions = self.llm.parse_command(text)
            log_event("LLM_PARSE", f"actions={len(actions)}")
            if self._stop_event.is_set() and self._cancel_requested:
                log_event("INTERACTION_CANCELLED", "after parse")
                return
            # 4) Execute actions
            results = self.executor.execute_actions(actions)
            log_event("EXECUTE_COMPLETE", f"results={len(results)}")
            if self._stop_event.is_set() and self._cancel_requested:
                log_event("INTERACTION_CANCELLED", "after execute")
                return
            # 5) Feedback mapping
            for r in results:
                if self._stop_event.is_set() and self._cancel_requested:
                    break
                t = r.get("action")
                if t == "play_music":
                    msg = (r.get("detail", "音乐播放已处理") if r.get("success") else f"音乐播放失败：{r.get('detail')}")
                elif t == "write_article":
                    msg = (r.get("detail", "文章已生成并保存") if r.get("success") else f"写作失败：{r.get('detail')}")
                elif t == "file_op":
                    msg = (r.get("detail", "文件操作已完成") if r.get("success") else f"文件操作失败：{r.get('detail')}")
                elif t == "app_control":
                    msg = (r.get("detail", "应用操作已完成") if r.get("success") else f"应用操作失败：{r.get('detail')}")
                elif t == "system_control":
                    msg = (r.get("detail", "系统设置已调整") if r.get("success") else f"系统操作失败：{r.get('detail')}")
                elif t == "web_search":
                    msg = (r.get("detail", "已打开搜索页") if r.get("success") else f"搜索失败：{r.get('detail')}")
                elif t == "navigate":
                    msg = (r.get("detail", "已打开目标页面") if r.get("success") else f"导航失败：{r.get('detail')}")
                elif t == "browser_click":
                    msg = (r.get("detail", "已点击目标链接") if r.get("success") else f"点击失败：{r.get('detail')}")
                elif t == "needs_clarification":
                    msg = r.get("detail", "需要进一步澄清指令")
                else:
                    msg = r.get("detail") or f"未知动作: {t}"
                self.speaker.speak(msg)
        except Exception as e:
            try:
                log_event("INTERACTION_ERROR", f"{e}", level=logging.ERROR)
                self.speaker.speak(f"执行失败：{e}")
            except Exception:
                pass
        finally:
            self.interacting = False
            log_event("INTERACTION_END", "voice flow finished")


class PetApp:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Desktop Pet")
        self.root.overrideredirect(True)
        self.root.attributes("-topmost", True)
        # Transparent background
        self.bg_color = "#00FF00"
        self.root.attributes("-transparentcolor", self.bg_color)

        self.canvas = tk.Canvas(self.root, width=200, height=200, highlightthickness=0, bg=self.bg_color)
        self.canvas.pack()

        # Drag state
        self._dragging = False
        self._drag_start_x = 0
        self._drag_start_y = 0
        self._win_start_x = 0
        self._win_start_y = 0
        self._mouse_down_time = 0.0
        self._drag_threshold = 3

        # Close button state
        self._hover = False
        self._close_btn_id: Optional[int] = None
        self._close_btn_size = 16
        self._close_btn_margin = 6
        self._close_btn_angle_deg = -38

        # Speaking mouth animation state
        self._mouth_open = False
        # Debounce for mouse up
        self._last_mouse_up_time = 0.0

        # Controller
        self.controller = PetController()
        # connect TTS callbacks
        self.controller.speaker.add_speak_start_listener(lambda: self._on_speak_start())
        self.controller.speaker.add_speak_end_listener(lambda: self._on_speak_end())

        # Bind events
        self.canvas.bind("<Enter>", self._on_enter)
        self.canvas.bind("<Leave>", self._on_leave)
        self.canvas.bind("<Button-1>", self._on_mouse_down)
        self.canvas.bind("<B1-Motion>", self._on_mouse_move)
        # 仅绑定根窗口的释放事件，避免重复触发
        self.root.bind("<ButtonRelease-1>", self._on_mouse_up)

        self.draw_pet()
        self._animate()

        # Initial position
        self.root.geometry("+200+200")
        log_event("PET_INIT", "initialized UI at +200+200")

    # UI draw
    def draw_pet(self):
        self.canvas.delete("all")
        # Body bounds
        bx1, by1, bx2, by2 = 30, 40, 170, 180
        # Body
        self.canvas.create_oval(bx1, by1, bx2, by2, fill="#FFD39B", outline="")
        # Eyes
        self.canvas.create_oval(70, 90, 90, 110, fill="#000000", outline="")
        self.canvas.create_oval(110, 90, 130, 110, fill="#000000", outline="")
        # Cheeks
        self.canvas.create_oval(60, 120, 85, 145, fill="#FF8DAA", outline="")
        self.canvas.create_oval(115, 120, 140, 145, fill="#FF8DAA", outline="")
        # Mouth
        if self._mouth_open:
            self.canvas.create_oval(90, 130, 110, 150, fill="#FF0000", outline="")
        else:
            self.canvas.create_line(90, 140, 110, 140, fill="#7A3B3B", width=3)

        # Close button if hovered — circle tangent to head ellipse at top-right
        if self._hover:
            # Ellipse center and radii
            cx = (bx1 + bx2) / 2.0
            cy = (by1 + by2) / 2.0
            rx = (bx2 - bx1) / 2.0
            ry = (by2 - by1) / 2.0
            r = self._close_btn_size / 2.0
            theta = math.radians(getattr(self, "_close_btn_angle_deg", -38))
            # Point on ellipse boundary at angle theta
            px = cx + rx * math.cos(theta)
            py = cy + ry * math.sin(theta)
            # Radial unit vector
            dx = px - cx
            dy = py - cy
            l = math.hypot(dx, dy) or 1.0
            ux = dx / l
            uy = dy / l
            # Embed distance to be tangent inside: center moves inward by r
            cx_btn = px - ux * r
            cy_btn = py - uy * r
            x1 = cx_btn - r
            y1 = cy_btn - r
            x2 = cx_btn + r
            y2 = cy_btn + r
            self._close_btn_id = self.canvas.create_oval(x1, y1, x2, y2, fill="#EE4444", outline="")
            self.canvas.create_text(cx_btn, cy_btn, text="x", fill="white", font=("Arial", 10, "bold"))
        else:
            self._close_btn_id = None

    def _animate(self):
        self._mouth_open = not self._mouth_open if getattr(self, "_speaking", False) else False
        self.draw_pet()
        self.root.after(300, self._animate)

    # Speak animation hooks
    def _on_speak_start(self):
        self._speaking = True
        self._mouth_open = True
    
    def _on_speak_end(self):
        self._speaking = False
        self._mouth_open = False

    # Hover
    def _on_enter(self, _evt):
        self._hover = True
        self.draw_pet()
        log_event("PET_HOVER_ENTER", "hover true")

    def _on_leave(self, _evt):
        self._hover = False
        self.draw_pet()
        log_event("PET_HOVER_LEAVE", "hover false")

    # Drag + Click toggle interaction
    def _on_mouse_down(self, evt):
        self._mouse_down_time = time.time()
        # 初始不认为是拖拽，只有移动超过阈值才置为True
        self._dragging = False
        self._drag_start_x = evt.x_root
        self._drag_start_y = evt.y_root
        self._win_start_x = self.root.winfo_x()
        self._win_start_y = self.root.winfo_y()
        log_event("PET_MOUSE_DOWN", f"x={evt.x_root}, y={evt.y_root}")

    def _on_mouse_move(self, evt):
        dx = evt.x_root - self._drag_start_x
        dy = evt.y_root - self._drag_start_y
        moved = abs(dx) + abs(dy)
        if moved >= self._drag_threshold:
            if not self._dragging:
                self._dragging = True
                log_event("PET_DRAG_START", f"from=+{self._win_start_x}+{self._win_start_y}")
            new_x = self._win_start_x + dx
            new_y = self._win_start_y + dy
            self.root.geometry(f"+{new_x}+{new_y}")
            log_event("PET_DRAG_MOVE", f"x={new_x}, y={new_y}")

    def _point_in_close_root(self, x_root: int, y_root: int) -> bool:
        # Convert screen coords to canvas-local coords
        if not self._hover or self._close_btn_id is None:
            return False
        x = x_root - self.root.winfo_x()
        y = y_root - self.root.winfo_y()
        # Recompute circle center and radius identical to draw_pet
        bx1, by1, bx2, by2 = 30, 40, 170, 180
        cx = (bx1 + bx2) / 2.0
        cy = (by1 + by2) / 2.0
        rx = (bx2 - bx1) / 2.0
        ry = (by2 - by1) / 2.0
        r = self._close_btn_size / 2.0
        theta = math.radians(getattr(self, "_close_btn_angle_deg", -38))
        px = cx + rx * math.cos(theta)
        py = cy + ry * math.sin(theta)
        dx = px - cx
        dy = py - cy
        l = math.hypot(dx, dy) or 1.0
        ux = dx / l
        uy = dy / l
        cx_btn = px - ux * r
        cy_btn = py - uy * r
        # Circle hit-test
        return (x - cx_btn) * (x - cx_btn) + (y - cy_btn) * (y - cy_btn) <= r * r

    def _on_mouse_up(self, evt):
        now = time.time()
        # Debounce: ignore duplicated release within 250ms
        if (now - getattr(self, "_last_mouse_up_time", 0.0)) < 0.25:
            log_event("PET_MOUSE_UP_IGNORED", "debounce 250ms")
            return
        self._last_mouse_up_time = now

        click_duration = now - self._mouse_down_time
        log_event("PET_MOUSE_UP", f"x={evt.x_root}, y={evt.y_root}")

        # Close button click (use root coords to be robust)
        if self._point_in_close_root(evt.x_root, evt.y_root):
            log_event("PET_CLOSE", "close button clicked")
            self.root.destroy()
            return

        # Determine click vs drag by small threshold
        moved = abs(evt.x_root - self._drag_start_x) + abs(evt.y_root - self._drag_start_y)
        is_click = (click_duration < 0.4 and moved < self._drag_threshold)

        if is_click:
            # Toggle interaction: start if idle; stop (and process) if interacting
            if not self.controller.interacting:
                log_event("PET_CLICK_START_INTERACTION", "start")
                self.controller.start_interaction(max_duration=Config.INTERACTION_MAX_DURATION)
            else:
                log_event("PET_CLICK_STOP_INTERACTION", "stop")
                self.controller.finish_interaction()
        else:
            if self._dragging:
                log_event("PET_DRAG_END", f"x={self.root.winfo_x()}, y={self.root.winfo_y()}")
        # Reset drag state
        self._dragging = False

    def run(self):
        self.root.mainloop()


def run_pet():
    app = PetApp()
    app.run()